查看原文
其他

自定义Spring bean容器了解一下?实战及原理解读

SnoWalker Bella的技术轮子 2021-08-09

这是Bella酱的第 49 期分享


作者 | SnoWalker

来源 | http://dwz.date/b23B

作者介绍 | RocketMQ社区2019杰出贡献者


开发中经常有这样的场景:

根据某个类型标识走不同的业务逻辑,通常我们会使用if(type.equals(xxxxx)) 或者 switch语句来进行逻辑处理。

这样做当然是没什么问题的。

当业务逻辑变得越来越复杂,类型标识增多之后,难免会出现if判断增加,或者switch case分支变多,这样的代码往往会过于冗长,代码重复性较大,或者说逼格不够高。

本文介绍一种基于自定义Bean容器的开发方式,消除代码中的判断分支,提升代码可读性。

我们通过一个demo来看如何实现这种编码方式。

定义接口

首先定义一个接口,主要有两个方法:

public interface AbstractService<T> {

    /**
    * 返回serviceName
    * 作为bean选择标识
    * @return
    */

    String serviceName();

    /**
    * 具体的service方法
    * @param parm
    * @return
    */

    T execute(Object parm);
}

实现类需要实现serviceName,返回具体的类型,注意不同的bean实现类该返回值不能重复

execute方法为业务方法,这里只是做个示范,实际开发中可以是任意的通用业务方法。

实现接口

接着编写实现类,实现接口

ServiceAImpl标记类型为 ServiceA

@Component
public class ServiceAImpl implements AbstractService<DemoA> {

    @Override
    public String serviceName() {
        return "ServiceA";
    }

    @Override
    public DemoA execute(Object parm) {
        System.out.println("ServiceAImpl execute");
        return new DemoA().setName("DemoA");
    }
}

ServiceBImpl标记类型为 ServiceB
@Component
public class ServiceBImpl implements AbstractService<DemoB> {

    @Override
    public String serviceName() {
        return "ServiceB";
    }

    @Override
    public DemoB execute(Object parm) {
        System.out.println("ServiceBImpl execute");
        return new DemoB().setName("DemoB");
    }
}

编写自定义Bean上下文

这里是重头戏,我们需要编写一个Bean上下文,并注入AbstractService集合。

@Component
public class ServiceContext {

    // IService容器,key=serviceName,velue=实例
    private static Map<String, AbstractService> SERVICE_CONTEXT;

    @Autowired
    List<AbstractService> services;

    @PostConstruct
    void init() {
        SERVICE_CONTEXT = new ConcurrentHashMap<> ();
        if (services == null) {
            return;
        }
        // 将IService所有的实现类注册到serviceContext
        for(AbstractService service : services) {
            SERVICE_CONTEXT.put(service.serviceName(), service);
        }
        System.out.println(JSON.toJSONString(SERVICE_CONTEXT));
    }

    /**
    * 根据serviceName获取实例
    * @param serviceName
    * @return
    */

    public AbstractService getServiceImpl(String serviceName) {
        return SERVICE_CONTEXT.get(serviceName);
    }
}

其实注释已经很清楚了,首先定义一个Map,key为String,代表我们上文中接口返回的serviceName。

value为接口实现类bean实例。

接着通过@Autowired注入AbstractService集合,这里是一个List。当Spring容器初始化完成,会将AbstractService的实现类都加载到List中。

在@PostConstruct标记的初始化方法中,遍历 List<AbstractService>,并依次加载到我们初始化好的Map中。key=AbstractService.serviceName()的返回值,value为AbstractService实例。

定义一个getServiceImpl(String serviceName)提供给业务使用,能够让我们通过具体的serviceName标识获取到Bean实例。这也是为何serviceName不能重复的原因。

测试

到此主要的逻辑编写就完成了,我们编写一个测试类测试一下具体如何使用。

public static void main(String[] args) {
    ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
    // 获取bean Context
    ServiceContext serviceContext = applicationContext.getBean("serviceContext", ServiceContext.class);
    // 根据serviceName获取具体的接口实现类
    AbstractService serviceA = serviceContext.getServiceImpl("ServiceA");
    AbstractService serviceB = serviceContext.getServiceImpl("ServiceB");
    // 调用service方法
    serviceA.execute(null);
    serviceB.execute(null);
}

这里从Spring上下文中获取到ServiceContext,并通过具体的serviceName获取到对应的Bean实例,并调用实例的execute方法。执行结果如下:

ServiceAImpl execute
ServiceBImpl execute

可能这还不算很直观,我们模拟一个业务场景。

业务需要先判断serviceName,再根据具体的值选择不同的执行逻辑。

正常情况下,我们会这样编写业务代码:

if ("ServiceA".equals(serviceName)) {
    serviceA.execute()
    return;
}

if ("ServiceB".equals(serviceName)) {
    serviceB.execute()
    return;
}

...

如果有一百个serviceName,那么这里就要有100个if分支,switch也同理。

但是采取本文中的编码方式则只需要这么写:

...省略获取serviceContext过程,最简单的方法是通过@Autowired/@Resource注入...
AbstractService service = serviceContext.getServiceImpl(serviceName);
service.execute()

这样我们就只需要在新增serviceName类型后,开发一个对应的实现类即可。

如果是传统的编码方式,则除了新增service实现,还需要修改if/switch判断逻辑,不够灵活且容易出错。

这里其实就是开放封闭原则的体现。传统的方式对修改和扩展都是开放的,而这种方式则是对扩展开放,对修改封闭的。尤其适用于复杂业务场景的开发。

原理

简单讲一下原理。

Spring框架支持对集合类型进行依赖注入,对于集合类型依赖注入与查找起作用的ApplicationContext实现类为 ListableBeanFactory

我们看下源码是如何实现该特性的:

具体的逻辑在 org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency 这个方法中

打开该方法,重点关注下面这行

Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);

进入resolveDependency方法,看到下面这一行,跳入doResolveDependency方法

result = doResolveDependency(descriptor, requestingBeanName, 
autowiredBeanNames, typeConverter);

重点关注下面的逻辑

Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
 return multipleBeans;
}

此处的resolveMultipleBeans方法逻辑为,如果解析到了多个匹配条件的Bean,就直接返回解析结果。

那具体的解析结果又是什么呢?我们进入resolveMultipleBeans方法

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) 
{

    Class<?> type = descriptor.getDependencyType();
    // 数组类型
    if (type.isArray()) {
        Class<?> componentType = type.getComponentType();
        ResolvableType resolvableType = descriptor.getResolvableType();
        Class<?> resolvedArrayType = resolvableType.resolve();
        if (resolvedArrayType != null && resolvedArrayType != type) {
            type = resolvedArrayType;
            componentType = resolvableType.getComponentType().resolve();
        }
        if (componentType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (getDependencyComparator() != null && result instanceof Object[]) {
            Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
        }
        return result;
    }
    // 集合类型,如List set
    else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
        Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
        if (elementType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (getDependencyComparator() != null && result instanceof List) {
            ((List<?>) result).sort(adaptDependencyComparator(matchingBeans));
        }
        return result;
    }
    // Map类型
    else if (Map.class == type) {
        ResolvableType mapType = descriptor.getResolvableType().asMap();
        Class<?> keyType = mapType.resolveGeneric(0);
        if (String.class != keyType) {
            return null;
        }
        Class<?> valueType = mapType.resolveGeneric(1);
        if (valueType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        return matchingBeans;
    }
    else {
        return null;
    }
}

这里便是@Autowired注入集合类型的核心。

  • 首先判断注入类型,如果是数组、Collection、Map等类型,则注入元素数据,即查找与元素类型相同的Bean,并注入到集合中。

  • 这里重点强调下Map类型,我们能够看出,Map的 key 为Bean的 name,value 为 与定义的元素类型相同的Bean。

// Map的key
Class<?> keyType = mapType.resolveGeneric(0);
if (String.class != keyType) {
    return null;
}
// Map的value
Class<?> valueType = mapType.resolveGeneric(1);
if (valueType == null) {
    return null;
}

也就是说,如果业务上不依赖外部的type,那么我们可以直接注入一个Map集合,比如:

@Autowired
private Map<String, BeanInterface> map;

这样就能够将接口BeanInterface的实现都注入到Map中,key的值为具体Bean的name,value为Bean实例。

小结

本文中,我们通过案例与源码,全方位呈现了Spring对集合类型的注入方式。总结一下:

  1. Spring在注入集合类的同时,会将集合泛型类的实例填入集合中,作为集合的初始值。

  2. 对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。

  3. 对于List类型,可以通过@Order指定加入List的顺序。只需要在实现类中加入@Order(value) 注解即可 ,值越小越先被初始化越先被放入List

-END-


更多精彩文章

1.Java是如何实现Future模式的?万字详解!

2.学弟面试归来(已拿支付宝offer

3.多一份经验,少一次踩坑!jstack 命令使用经验总结

4.撸个框架考虑一下?超万字长文,精讲如何手写分布式事务框架

5.平滑迁移 Dubbo 服务的思考

6.面试官:小伙子,听说你看过ThreadLocal源码?万字图文深度解析ThreadLocal


如果你喜欢本文

请长按二维码,关注 Bella的技术轮子

转发至 朋友圈,是对我最大的支持

喜欢就点个在看

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存